package edu.fiu.cis.selfhealingdisk;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.TreeMap;

import edu.fiu.cis.selfhealingdisk.IORequest.IOEnum;

/**
 * @author vanvorst
 * 
 */
public class SimulationExecutive {
	private long currentTime;
	private final static long granularity = (long) 1E5;
	private final static int sectorSize = 512; 
	private final Map<Long, IORequest> outstanding;
	private final LinkedList<IORequest> pendingInterruptions = new LinkedList<IORequest>();
	
	private final ParityModel parityModel;
	private final DiskSimJNIInterface diskSim;
	
	private final String modelFile;
	private final String outputFile;
	
	private Configuration configuration;
	
	
	double mean = 0.0,x2 = 0.0, n = 0.0, variance = 0.0,p = 0.0;
	
	
	/**
	 * @param granularity
	 * @param parityModel
	 * @param config
	 * @param config
	 */
	public SimulationExecutive(ParityModel parityModel, Configuration config,
			String modelFile,String outputFile) {
		this.configuration = config;
		this.outstanding = new HashMap<Long, IORequest>();
		this.currentTime = 0;
		this.parityModel = parityModel;
		this.diskSim = new DiskSimJNIInterface();
		this.modelFile  = modelFile;
		this.outputFile = outputFile;
		this.parityModel.setInterruptions(this.pendingInterruptions);
	}

	/**
	 * 
	 */
	public Map<String,String> runSimulation() {
		TreeMap<String,String> stats = new TreeMap<String,String>();
		int i = 0;
		
		this.diskSim.startSimulation(modelFile, outputFile);
		while (true) {
			Event curEvent = parityModel.nextEvent(currentTime + granularity);
			switch (curEvent.getEventType()) {
			case DONE:
				finishSimulation();
				mean = mean/n;
				variance = (x2/n - mean*mean);
	

				///Configuration parameters
				stats.put("[P] Parity Group Size",Integer.toString(this.configuration.getParityGroupSize()));
				stats.put("[P] Mod Index",Integer.toString(this.configuration.getModIndex()));
				stats.put("[P] Parity Model", this.configuration.getParityFormat().toString() );
				stats.put("[P] Use Memory for Parity", Boolean.toString(this.configuration.getMemoryManagement()) );
				stats.put("[P] Flush Interval", Long.toString(this.configuration.getMaxTimeInMemory()));
				
				
				//Final Statistics
				stats.put("Original Requests Count", Double.toString(n));
				stats.put("Parity Requests Count", Double.toString(p));
				stats.put("Response Time Mean (excluding parity requests)", Double.toString(mean));
				stats.put("Response Time Variance (exclusing parity requests)", Double.toString(variance));
				stats.putAll(parityModel.getStats());
				
				DiskSimOutputParser statsParser = new DiskSimOutputParser(this.outputFile);
				stats.putAll(statsParser.parseStatistics());
				return stats;
			case IOREQUESTS:
				IORequestList requests = curEvent.getIORequests();
				for (IORequest r : requests) {
					outstanding.put(r.getRequestId(), r);
					int type = (r.getType().equals(IOEnum.WRITE))?DiskSimJNIInterface.DISKSIM_WRITE:DiskSimJNIInterface.DISKSIM_READ;
					diskSim.sendRequest(r.getRequestTime(),type, r.getLba(), r.getSize()*sectorSize, r.getRequestId());
					
					i++;
					if (i%500==0) System.out.print(".");
					if (i%5000==0) System.out.print("(" + i + " " + (currentTime/10E9) + " "+ this.outstanding.size() + ")" );
				}
				this.currentTime += granularity;
				break;
				
			case TIME_WARP:
				this.currentTime = curEvent.getNewTime();
				break;
			}
			updateEvents(this.currentTime);
		}
		
		
	}
	
	
	public void updateEvents(long timestamp) {
		this.diskSim.setTime(timestamp);
		long[] events = diskSim.getEvents();
		for (int i = 0; i < events.length/2; i++) {
			receiveIOResponseFromDiskSim(events[i*2], events[i*2+1]);
		}
	}

	public void finishSimulation() {
		this.diskSim.finishSimulation();
		long[] events = diskSim.getEvents();
		for (int i = 0; i < events.length/2; i++) {
			receiveIOResponseFromDiskSim(events[i*2], events[i*2+1]);
		}

	}
	

	public void receiveIOResponseFromDiskSim(long requestId,long timestamp) {
		IORequest ior = this.outstanding.remove(requestId);
		ior.setServiceTime(timestamp);
		if (ior.isParity()) {
			this.pendingInterruptions.add(ior);
		}
		
		if (!ior.isParity()) {
			double diff = (ior.getServiceTime()/10E6 -ior.getRequestTime()/10E6);
			mean += diff;
			x2 += diff*diff;
			n++;
		} else {
			p++;
		}
	}
}
